package zachg.survival;

import java.awt.Point;
import java.util.ArrayList;

import zachg.survival.SurvivalObject.GameCharacter;

import com.threerings.io.SimpleStreamableObject;
import com.threerings.presents.dobj.DSet;
import com.threerings.util.Name;

/**
 * SurvialLogic does the following:
 * -Determines if moves are legal,
 * -Determines if players can attack or mine
 * -Holds the creeper movement AI
 * -Generates the AI locations
 * -Contains possible acts of god, and chooses random ones.
 */
public class SurvivalLogic
{
    public SurvivalLogic (int size)
    {
        _size = size;
    }
 
    /**
     * Returns true if the supplied piece represents a legal move for the
     * owning player.
     */
    public boolean isLegalMove (SurvivalObject.GameCharacter cursorCharacter, SurvivalObject.GameCharacter gameCharacter)
    {
    	if(gameCharacter == null || cursorCharacter == null){
    		return false;
    	}
    	int dx = Math.abs(cursorCharacter.x - gameCharacter.x);
    	int dy = Math.abs(cursorCharacter.y - gameCharacter.y);
    	if( 	dx <= moveDistance &&
    			dy <= moveDistance
			){
    		return true;
    	} else {
    		return false;
    	}    	
    }
    
    /**
     * Determines if a player can mine a given location
     *
     * @param gameCharacter the game character
     * @param mineLocations the mine locations
     * @return true, if successful
     */
    public boolean canMine(SurvivalObject.GameCharacter gameCharacter, int[] mineLocations)
    {
        for( int i = 0 ; i < mineLocations.length ; i++ ){
        	if(i%2 == 0){
        		int Xlocation = mineLocations[i];
        		int Ylocation = mineLocations[i+1];
        		
        		int dx = (gameCharacter.x - Xlocation);
        		int dy = (gameCharacter.y - Ylocation);
        		double distance = Math.sqrt( dx*dx + dy*dy );
        		if(distance == 0){
        			return true;
        		}
        	}
        }
    	return false;
    }

    /**
     * Determines if a player can attack at the given location
     * NOTE: 	Attack range is fixed at 1 right now. Could be more flexible. 
     * 			I thought about doing that, but felt it would be better with 
     * 			a distance of one, just for simplicity's sake.
     *
     * @param gameCharacters the game characters
     * @param attackingGameCharacter the attacking game character
     * @return true, if successful
     */
    public boolean canAttack(DSet<GameCharacter> gameCharacters, SurvivalObject.GameCharacter attackingGameCharacter)
    {
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
			int dx = (gameCharacter.x - attackingGameCharacter.x);
			int dy = (gameCharacter.y - attackingGameCharacter.y);
			double distance = Math.sqrt( dx*dx + dy*dy );
        	if(distance <= 1 && gameCharacter.playerOwner.equals(attackingGameCharacter.playerOwner) == false){
        		return true;
        	}    		
    	}
    	return false;
    }    
    
    /**
     * Method figures out which player is being attacked when given two characters.
     * NOTE: if the attacking player can attack a number of different characters, 
     * this will return only the last accessed character in gameCharacters
     * TODO: extend method to more intelligently pick a character to attack when several attack options are available. 
     *
     * @param gameCharacters the game characters
     * @param attackingGameCharacter the attacking game character
     * @return the survival object. game character
     */
    public SurvivalObject.GameCharacter attackWhichCharacter(DSet<GameCharacter> gameCharacters, SurvivalObject.GameCharacter attackingGameCharacter)
    {
    	for (SurvivalObject.GameCharacter gameCharacter : gameCharacters) {
			int dx = (gameCharacter.x - attackingGameCharacter.x);
			int dy = (gameCharacter.y - attackingGameCharacter.y);
			double distance = Math.sqrt( dx*dx + dy*dy );
        	if(distance < 2 && gameCharacter.playerOwner.equals(attackingGameCharacter.playerOwner) == false){
        		return gameCharacter;
        	}    		
    	}
    	return null;
    }    
    
    /**
     * Spawns a new character at random position.
     *
     * @param characterOwner the character owner
     * @param playerOwner the player owner
     * @param gameObj the game obj
     * @return the survival object. game character
     */
    public SurvivalObject.GameCharacter spawnAtRandomPosition(int characterOwner,Name playerOwner, SurvivalObject gameObj)
    {
    	Point randomLocation = new Point((int)(Math.random()*SurvivalObject.GAME_SIZE), (int)(Math.random()*SurvivalObject.GAME_SIZE));
    	while( 	gameObj.getCharacterAtLocation(randomLocation.x, randomLocation.y) != null && 
    			getCharacterAtLocation(randomLocation.x, randomLocation.y) != null
    			){
    		randomLocation = new Point((int)(Math.random()*SurvivalObject.GAME_SIZE), (int)(Math.random()*SurvivalObject.GAME_SIZE));
    	}
    	SurvivalObject.GameCharacter newCharacter = spawnAtPosition(characterOwner,playerOwner,randomLocation);
    	return newCharacter;
    }
    
    /**
     * Spawns a new character at position a given location.
     *
     * @param characterOwner the character owner
     * @param playerOwner the player owner
     * @param location the location
     * @return the survival object. game character
     */
    public SurvivalObject.GameCharacter spawnAtPosition(int characterOwner,Name playerOwner, Point location)
    {
    	SurvivalObject.GameCharacter gameCharacter = new SurvivalObject.GameCharacter();
    	gameCharacter.ownerType = characterOwner;
    	gameCharacter.playerOwner = playerOwner;
    	gameCharacter.x = location.x;
    	gameCharacter.y = location.y;
    	return gameCharacter;    	
    }
    
    /**
	 * Sets the initial creeper properties, scale from 1-scaleStrength as game goes on.
	 *
	 * @param creeper the creeper
	 * @param PercentageGameComplete the percentage game complete
	 * @return the survival object. game character
	 */
	public SurvivalObject.GameCharacter setInitialCreeperProperties(SurvivalObject.GameCharacter creeper, double PercentageGameComplete)
    {
		int creeperScaleStrength = 5;
		creeper.currentDamage = (int)(1+PercentageGameComplete*creeperScaleStrength);
		creeper.currentHealth = (int)(1+PercentageGameComplete*creeperScaleStrength);
		creeper.killStreak = 0;
		return creeper;
    }
    	
    
    /**
     * Determines where each creep should move this turn
     *
     * @param _gameObj the Distributed object
     */
    public void creeperTurn (SurvivalObject _gameObj)
    {
    	//move each creep to be adjacent to nearest player character 
    	for( SurvivalObject.GameCharacter currentCreep : _creeps ){
    		//find nearest player character
    		double nearestDistance = -1;
    		SurvivalObject.GameCharacter nearestGameCharacter = new SurvivalObject.GameCharacter();
        	for (SurvivalObject.GameCharacter gameCharacter : _gameObj.gameCharacters) {
        		if(gameCharacter.ownerType != SurvivalObject.ENEMY_ID){
        			int dx = (gameCharacter.x - currentCreep.x);
        			int dy = (gameCharacter.y - currentCreep.y);
        			double distance = Math.sqrt( dx*dx + dy*dy );
        			if(nearestDistance == -1){
        				nearestDistance = distance;
        				nearestGameCharacter = gameCharacter;
        			} else if(nearestDistance > distance){
        				nearestDistance = distance;
        				nearestGameCharacter = gameCharacter;
        			}
        		}
        	}
    		//move creep towards this player character
        	//NOTE: keep creep movement to one square for now, simple
			int dx = (nearestGameCharacter.x - currentCreep.x);
			int dy = (nearestGameCharacter.y - currentCreep.y);
			double distance = Math.sqrt( dx*dx + dy*dy );
        	//move the creep... but make sure there's no other creeps
			//or player characters where it wants to move.
			//if it's obstructed, it just won't move. You would need pathfinding 
			//to find another route, and I've got no time to go that trouble right now
			//TODO: if creep's desired path is obstructed, use pathfinding to get another route.
			if( distance > 1 ){
    			if(nearestGameCharacter.x < currentCreep.x){
    				if(	_gameObj.getCharacterAtLocation(currentCreep.x-1, currentCreep.y) == null &&
    					getCharacterAtLocation(currentCreep.x-1, currentCreep.y) == null
    						){
    					currentCreep.x--;
    				}
    			} else if(nearestGameCharacter.x > currentCreep.x){
    				if(	_gameObj.getCharacterAtLocation(currentCreep.x+1, currentCreep.y) == null &&
    					getCharacterAtLocation(currentCreep.x+1, currentCreep.y) == null
    						){
    					currentCreep.x++;
    				}
    			}
    			if(nearestGameCharacter.y < currentCreep.y){
    				if(	_gameObj.getCharacterAtLocation(currentCreep.x, currentCreep.y-1) == null &&
    					getCharacterAtLocation(currentCreep.x, currentCreep.y-1) == null
    						){
    					currentCreep.y--;
    				}
    			} else if(nearestGameCharacter.y > currentCreep.y){
    				if(	_gameObj.getCharacterAtLocation(currentCreep.x, currentCreep.y+1) == null &&
    					getCharacterAtLocation(currentCreep.x, currentCreep.y+1) == null
    						){
    					currentCreep.y++;
    				}
    			}
        	}
    	}
    }
    
    /**
     * Gets the creep at location.
     * NOTE: an identical method is in SurvivalObject. Ideally we'd only have to use that one, 
     * but we need this here because we store the creep data here. This is bad practice, sorry :(
     * TODO: Creep data should be moved to the SurvivalObject, where it belongs, and then the method there should be extended to look through creeps if asked.
     *
     * @param x the x
     * @param y the y
     * @return the character at location
     */
    public SurvivalObject.GameCharacter getCharacterAtLocation(int x, int y)
    {
    	for (SurvivalObject.GameCharacter creepCharacter : _creeps) {
    		if(creepCharacter.x == x && creepCharacter.y == y){
    			return creepCharacter;
    		}
    	}    	
    	return null;
    }
    
    /**
     * Checks if is time for a death event to occur
     *
     * @param currentTurn the current turn
     * @return true, if is time for death event
     */
    public boolean isTimeForDeathEvent(int currentTurn)
    {
    	Log.info("isTimeForDeathEvent, currentTurn: "+currentTurn);
    	if(currentTurn%timeTillActOfGod == 0 && currentTurn != 0){
    		Log.info("true");
    		return true;
    	} else {
    		return false;
    	}
    }
    
    /**
     * Chooses a random death event. This is a simple method
     * that could be extended like mad. It gets it's own method for that reason 
     *
     * @return the act of god
     */
    public ActOfGod chooseRandomDeathEvent()
    {
    	return actsOfGod[(int)(Math.random()*actsOfGod.length)];
    }

    /**
     * In bounds......?
     *
     * @param x the x
     * @param y the y
     * @return true, if successful
     */
    protected final boolean inBounds (int x, int y)
    {
        return x >= 0 && y >= 0 && x < _size && y < _size;
    }
    
    /**
     * ACTS OF GOD........muahahaha
     * Every so often, the strongest character will die to make sure nobody completely 
     * dominates the game and there's an opportunity for a player to stage a comeback.
     * This class contains information about the ways that dominating character will die.
     */
    public static class ActOfGod extends SimpleStreamableObject
    {
        
        /** NOTE: as of may 29th, 2011, damage isn't used in the game. It's in here for future extensions. */
        public int damage;
        public String name, description;
        public int x, y, size;
        public boolean isPlayer;
        
        public ActOfGod (String _name, String _description, int _damage){
        	name = _name;
        	description = _description;
        	damage = _damage;
        }
    }    
    
    /** Contains all the acts of god that can occur in this game */
    public static final ActOfGod[] actsOfGod = {
    	new ActOfGod("Tornado", " was sucked up into a deadly tornado vortex.", 100),
    	new ActOfGod("Earthquake", "was shook to death by an earthquake.", 100),
    	new ActOfGod("Dysentry", "has died of dysentry", 100),
    	new ActOfGod("Smallpox", "broke out in smallpox and fell dead.", 100)
    };
    
    /** The _size. Needed for the inBounds method */
    protected int _size;
    
    /** All the AI -controlled creeps in the game */
    protected ArrayList<SurvivalObject.GameCharacter> _creeps = new ArrayList<SurvivalObject.GameCharacter>();
    
    /** The number of turns until an act of god. will occur */
    protected int timeTillActOfGod = 15;
    
    /** The number of mines. present in the game*/
    protected int numMines = 15;
    
    /** The move distance of PLAYER characters. Creeps only move 1 square */
    protected int moveDistance = 3;
    
    /** The number of creeps that appear every time they get a turn */
    public int creepsPerTurn = 1;
}